package hw02;

import hw01.Questions;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import javax.servlet.http.HttpServletRequest;

import org.w3c.dom.Element;
import org.w3c.dom.Text;


/**
 * The entry point to the application.
 * 
 * @author Joshua Belanger
 * @version 1.0
 */
public class ResultPage extends WebPage {

	List<Exception> exceptions = new LinkedList<Exception>();
	
	public ResultPage(HttpServletRequest request) {
		super(request);		
	}

	@Override
	protected void initDocument() {
		super.initDocument();

		String functionName = request.getParameter("function");
		
		Element title = document.createElement("title");
		title.setTextContent("Result Page - " + functionName);
		head.appendChild(title);

		body.appendChild(createResults());
		body.appendChild(createDiagnostics());
	}
	
	protected Element createDiagnostics() {
		Element diagnosticsDiv = createSection("Diagnostics");
		if(exceptions.size() > 0) {
			Element ul = document.createElement("ul");
			diagnosticsDiv.appendChild(ul);
			for(Exception e : exceptions) {
				Element li = document.createElement("li");
				if(e.getMessage() != null) {
					li.setTextContent(e.getMessage());
				} else {
					ul.appendChild(li);
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					PrintStream ps = new PrintStream(baos, true);
					e.printStackTrace(ps);
					li.setTextContent(baos.toString());
				}
			}
		}
		return diagnosticsDiv;
	}
	
	protected Element createResults() {
		Element databaseResultsDiv = createSection("Database Results");
		
		String functionName = request.getParameter("function");
		
		Element table = document.createElement("table");
		databaseResultsDiv.appendChild(table);
		
		Element tr = document.createElement("tr");
		table.appendChild(tr);
		
		for(String paramName : Meta.methodOutput.get(functionName)) {
			Element th = document.createElement("th");
			th.setTextContent(paramName);
			tr.appendChild(th);
		}
		
		String ret = null;
		try {
			Method method = Meta.methods.get(functionName);
			List<Object> params = new LinkedList<Object>();
			Iterator<String> paramNameIter = Meta.methodInput.get(functionName).iterator();
			Class<?>[] paramTypes = method.getParameterTypes();
			for(int i=0; i<paramTypes.length; i++) {
				String paramName = paramNameIter.next();
				String paramInput = request.getParameter(paramName);
				Class<?> paramType = paramTypes[i];
				if(paramType.isAssignableFrom(int.class) || paramType.isAssignableFrom(Integer.class)) {
					params.add(Integer.parseInt(paramInput));
				} else if(paramType.isAssignableFrom(String.class)) {
					params.add(paramInput);
				} else if(paramType.isAssignableFrom(Timestamp.class)) {
					params.add(Timestamp.valueOf(paramInput));
				}
			}
			ret = invoke(method, params.toArray());
		} catch (Exception e) {
			exceptions.add(e);
		}
		
		if(ret != null) {
			Scanner lineScan = new Scanner(ret);
			if(lineScan.hasNextLine()) {
				while(lineScan.hasNextLine()) {
					String line = lineScan.nextLine();
					tr = document.createElement("tr");
					table.appendChild(tr);
					
					Scanner eleScan = new Scanner(line);
					eleScan.useDelimiter(Questions.SEPARATOR);
					List<String> paramOutput = Meta.methodOutput.get(functionName);
					Iterator<String> paramOutputIter = paramOutput.iterator();
					
					while(eleScan.hasNext() && paramOutputIter.hasNext()) {
						String paramName = paramOutputIter.next();
						String paramValue = eleScan.next();
						Element td = document.createElement("td");
						tr.appendChild(td);
						Map<String, String> linkHref = Meta.linkedParams.get(paramName);
						if(linkHref != null) {
							linkHref.put(paramName, paramValue);
							Element a = document.createElement("a");
							a.setTextContent(paramValue);
							a.setAttribute("href", "?" + encode(linkHref));
							td.appendChild(a);
						} else {
							td.setTextContent(paramValue);
						}
					}
				}
				
			} else if(Meta.methodOutput.get(functionName).size() > 0) {
				Element caption = document.createElement("caption");
				caption.setTextContent("*No Results*");
				table.appendChild(caption);
			}
		}
		
		return databaseResultsDiv;
	}

	@Override
	protected Element createIdentInfo() {
		Element identInfoDiv = super.createIdentInfo();
		
		String functionName = request.getParameter("function");
		
		Element functionCall = document.createElement("span");
		identInfoDiv.appendChild(label("Invocation:", functionCall));

		Text functionLabel = document.createTextNode(functionName);
		functionCall.appendChild(functionLabel);
		
		Text openParen = document.createTextNode("(");
		functionCall.appendChild(openParen);
		
		List<String> parameterNames = Meta.methodInput.get(functionName);
		Iterator<String> iter = parameterNames.iterator();
		while(iter.hasNext()) {
			String parameterName = iter.next();
			String paramValue = request.getParameter(parameterName);
			if(paramValue == "") {
				paramValue = " ";
			}
			
			Element param = document.createElement("em");
			param.setTextContent(paramValue);
			functionCall.appendChild(param);
			
			if(iter.hasNext()) {
				Text comma = document.createTextNode(", ");
				functionCall.appendChild(comma);
			}
		}
		
		Text closeParen = document.createTextNode(")");
		functionCall.appendChild(closeParen);
	
		return identInfoDiv;
	}
	
	/**
	 * 
	 * @param method
	 * @param args
	 * @return
	 * @throws Exception
	 */
	protected String invoke(Method method, Object... args) throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		PrintStream newOut = new PrintStream(baos, true);
		PrintStream oldOut = System.out;
		System.setOut(newOut);

		Object result = method.invoke(RoomBoss.roomboss, args);
		String output = baos.toString();

		System.setOut(oldOut);
		
		if(result == null) {
			return output;
		}
		return result.toString();
	}

	
}
